﻿// Copyright © 2011 Paul Vanukoff (pvanukoff@gmail.com)

using System;
using System.Collections.Generic;
using Bravura.Contracts.PluginTypes;
using Bravura.Core.Common;
using Bravura.Core.PluginLib;
using Bravura.Native.PluginBase;
using Bravura.Native.PluginBase.Oscillators;
using Bravura.Native.PluginBase.Waveforms;

namespace Bravura.Native.Plugins.Instruments
{
	/// <summary>
	/// Output channels : 2 
	/// Voices : 10
	/// 
	/// Multi-oscillator synth. Each voice has one or more "parts" and each
	/// part has two oscillators (one for each output channel). The phases of each oscillator
	/// are different and this creates a "full" sound. The waveforms used are regenerated as
	/// needed to perform band-limiting.
	/// </summary>
	public class Toner : Instrument<Toner.TonerVoice>, IInstrument
	{
		#region Configuration Properties

		public List<IConfigItem> PartConfigs
		{
			get;
			set;
		}

		#endregion

		public Toner(IAudioPluginHost host, int sampleRate) : base(host, sampleRate, 10)
		{
		}

		public override Toner.TonerVoice CreateVoice()
		{
			return new TonerVoice(_sampleTime, 1.0 / AttackTime, 1.0 / ReleaseTime, Distortion,
				PartConfigs);
		}

		public class TonerVoice : Voice
		{
			#region Fields

			// configuration values
			private int _numParts = 0;
			private TonerVoicePart[] _parts = new TonerVoicePart[0];

			#endregion

			internal TonerVoice(double dt, double attRate, double relRate, double dist,
				List<IConfigItem> partConfigs)
				: base(dt, attRate, relRate, dist)
			{
				if (partConfigs != null && partConfigs.Count > 0)
				{
					_numParts = partConfigs.Count;
					_parts = new TonerVoicePart[_numParts];
					for (int p = 0; p < _numParts; p++)
					{
						_parts[p] = new TonerVoicePart(dt, partConfigs[p]);
					}
				}
				else
				{
					_numParts = 1;
					_parts = new TonerVoicePart[1];
					_parts[0] = new TonerVoicePart(dt, null);
				}
			}

			public override void NoteOn(double sound, double pitch, double velocity)
			{
				base.NoteOn(sound, pitch, velocity);
				for (int p = 0; p < _numParts; p++)
				{
					_parts[p].NoteOn(_freqOrd);
				}
			}

			public override void NoteOff()
			{
				base.NoteOff();
				for (int p = 0; p < _numParts; p++)
				{
					_parts[p].NoteOff();
				}
			}

			public override double[] Sample()
			{
				double[] partSample = null;
				double s0 = 0.0;
				double s1 = 0.0;

				// calculate sample value
				for (int p = 0; p < _numParts; p++)
				{
					partSample = _parts[p].Sample();
					s0 += partSample[0];
					s1 += partSample[1];
				}

				// apply distortion ?
				if (_dist != 1.0)
				{
					s0 = (s0 < 0.0) ? -Math.Pow(-s0, _dist) : Math.Pow(s0, _dist);
					s1 = (s1 < 0.0) ? -Math.Pow(-s1, _dist) : Math.Pow(s1, _dist);
				}

				// apply level
				s0 *= _velocity * _level * _level;
				s1 *= _velocity * _level * _level;

				// update voice envelope
				UpdateEnvelope();

				// load sample
				_sample[0] = _mix0 * s0;
				_sample[1] = _mix1 * s1;

				// return sample
				return _sample;
			}

			private class TonerVoicePart
			{
				#region Fields

				private bool _usesBasicDtWf = false;
				private bool _usesNoiseDtWf = false;
				private int _lastSize = 0;
				private double _decay = 2.0;
				private double _phaser = 0.0;
				private double _combFreq = 0.0;
				private double _combPhase = 0.0;
				private int _seed = 0;
				private DataTableWaveform _dtwf = null;
				private Oscillator _oscillator0 = null; // left
				private Oscillator _oscillator1 = null; // right
				private double _sr = 48000;
				double[] _sample = new double[2];

				#endregion

				public TonerVoicePart(double dt, IConfigItem config)
				{
					_sr = 1.0 / dt;

					string detuneArg = "1.0";
					string ampArg = "1.0";
					string oscType = "Waveform";
					string waveformArgsType = "Basic";
					string waveformArgsParams = "2.0,0.0,0.0,0.0";

					#region Parse Config

					if (config != null)
					{
						foreach (IConfigItem item in config.GetChildItems())
						{
							string itemName = item.GetName();
							if (itemName == "OscType")
							{
								oscType = item.GetValue();
							}
							else if (itemName == "WaveformArgs")
							{
								foreach (IConfigItem wfArgItem in item.GetChildItems())
								{
									string wfArgItemName = wfArgItem.GetName();
									if (wfArgItemName == "Type")
									{
										waveformArgsType = wfArgItem.GetValue();
									}
									else if (wfArgItemName == "Params")
									{
										waveformArgsParams = wfArgItem.GetValue();
									}
								}
							}
							else if (itemName == "Detune")
							{
								detuneArg = item.GetValue();
							}
							else if (itemName == "Amp")
							{
								ampArg = item.GetValue();
							}
						}
					}

					#endregion

					#region Apply Config

					if (oscType == "Waveform")
					{
						// get wf arguments
						string[] waveformArgsVals = waveformArgsParams.Split(',');

						// parse wf arguments
						if (waveformArgsType == "Basic")
						{
							_usesBasicDtWf = true;
							_decay = double.Parse(waveformArgsVals[0]);
							_phaser = double.Parse(waveformArgsVals[1]);
							_combFreq = double.Parse(waveformArgsVals[2]);
							_combPhase = double.Parse(waveformArgsVals[3]);
						}
						else if (waveformArgsType == "Noise")
						{
							_usesNoiseDtWf = true;
							_decay = double.Parse(waveformArgsVals[0]);
							_seed = Utilities.Random.Next();
						}

						// create empty dtwf
						_dtwf = new DataTableWaveform();

						// create oscs
						_oscillator0 = new WaveformOscillator(dt, _dtwf, Utilities.Random.NextDouble());
						_oscillator1 = new WaveformOscillator(dt, _dtwf, Utilities.Random.NextDouble());
					}
					else if (oscType == "Noise")
					{
						// create oscs
						_oscillator0 = new NoiseOscillator(dt, null, Utilities.Random.NextDouble());
						_oscillator1 = new NoiseOscillator(dt, null, Utilities.Random.NextDouble());
					}

					// set detune
					_oscillator0.ModulateFrequency(double.Parse(detuneArg));
					_oscillator1.ModulateFrequency(double.Parse(detuneArg));

					// set amplitude
					_oscillator0.ModulateAmplitude(double.Parse(ampArg));
					_oscillator1.ModulateAmplitude(double.Parse(ampArg));

					#endregion
				}

				public void NoteOn(double freq)
				{
					// rebuilding the waveform data as needed to do band-limiting
					if (_usesBasicDtWf || _usesNoiseDtWf)
					{
						int size = (int)(_sr / freq);
						if (size != _lastSize)
						{
							double[] wfData = null;
							if (_usesBasicDtWf)
							{
								wfData = Utilities.BuildFourierBasicWaveform(size, _decay, _phaser, _combFreq, _combPhase);
							}
							else
							{
								wfData = Utilities.BuildFourierNoiseWaveform(size, _decay, _seed);
							}
							_dtwf.SetData(wfData);
							_lastSize = size;
						}
					}

					// set the frequency
					_oscillator0.SetFrequency(freq);
					_oscillator1.SetFrequency(freq);
				}

				public void NoteOff()
				{
				}

				public double[] Sample()
				{
					_sample[0] = _oscillator0.Sample();
					_sample[1] = _oscillator1.Sample();
					return _sample;
				}
			}
		}
	}
}
